MySqlException (0x80004005): Lock wait timeout exceeded; try restarting transaction

—— TransactionScope 为什么会产生空语句事务?

momo314相同方式共享非商业用途署名转载



一丶 背景

今天发现在一个5分钟一次的作业中发现一个异常:

MySql.Data.MySqlClient.MySqlException (0x80004005): Lock wait timeout exceeded; try restarting transaction

经过搜索,该问题一般是由于使用数据库事务时发生死锁,导致事务无限期等待,最终超时。所以,一味的增加 innodb_lock_wait_timeout 的值并没有什么用,甚至,反而会导致问题更加严重。

二、问题排查及解决方案

既然是事务发生了死锁,那就好查了,直接从 information_schema.innodb_trx 查询即可:

SELECT * 
FROM `information_schema`.`innodb_trx` 
ORDER BY `trx_started`

结果如下:

innodb_trxinnodb_trx


注意,其中两条 LOCK WAIT 的记录就是作业中执行的事务,从 trx_query 中可以看到,其实SQL语句很简单,根据主键更新一条记录而已,按理说,没道理会慢到5分钟都执行不完,然后导致超时的。

但是,先不管,我们先把这两条记录对应的 trx_mysql_thread_id kill掉,再观察下是否正常再说。

很显然,问题并没有解决,作业下一次执行的时候又会发生死锁。

这时,才开始怀疑,oms_orders 表的 id: 2182 这条记录可能是被锁住了,导致无法更新,所以才会导致事务一直等待,最终超时。那么,被谁锁住了呢,检查刚才查询出来的记录,发现并没有 oms_orders 相关的语句,很奇怪。

经大佬提醒,注意到了前两条记录,他们的事务隔离级别 trx_isolation_level 与作业中的事务的隔离级别相同,都为 SERIALIZABLE(我们通常使用的是 REPEATABLE READ), 但他们的 trx_query 却是空的,所以并不知道这两个事务中到底执行了什么语句,也就无法确定是不是这两个事务锁住了我们要操作的那条记录。不过还好,看起来,这两个事务已经执行了相当长的时间(trx_started),肯定有问题,kill掉应该没什么大问题,毕竟,万一真是他们俩给锁住的呢?

-- 还好,他俩的 thread_id 字段不是空的
kill <trx_mysql_thread_id>

果然,作业下一次执行的时候没有被卡住,事务顺利执行成功,问题也算是解决了。

三丶 猜想

我们程序中使用了 TransactionScope 来创建事务,相关代码如下:

var options1 = TransactionScopeOption.RequiresNew;
var options2 = TransactionScopeAsyncFlowOption.Enabled;
using (var transaction = new TransactionScope(options1, options2))
{
    // database operation here
    // ...

    transaction.Complete();
}

经验证,这种方式创建的事务的隔离级别确实是 SERIALIZABLE, 猜测是因为 TransactionScope 需要支持多数据库多连接的事务回滚操作,所以使用了更高的事务隔离级别。

另外,猜测 TransactionScope 在某些特殊情况下被非正常结束可能会导致产生上面那种空语句的的事务,但具体原因尚不明确,并没有重现。

✎﹏ 本文来自于 momo314和他们家的猫,文章原创,转载请注明作者并保留原文链接。